package wml.clipstore.map;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import wml.clipstore.base.Entity;
import wml.clipstore.utils.EntityUtils;

public class MapManager {

	@SuppressWarnings("rawtypes")
	private static List<Class> storableType = new ArrayList<Class>();
	private static MapManager manager;
	private Map<Class<? extends Entity>, List<Field>> storableFields = new HashMap<Class<? extends Entity>, List<Field>>();

	private List<String> classes;
	private Map<Class<?>, List<Field>> referringFields = new HashMap<Class<?>, List<Field>>();
	private List<String> rules;

	static {
		storableType.add(String.class);
		storableType.add(Number.class);
		storableType.add(Boolean.class);
		storableType.add(Character.class);

		// storableType.add(Identifier.class);
		// storableType.add(Entity.class);
		// storableType.add(Collection.class);
	}

	private MapManager() {
		loadClasses();
	}

	private void loadClasses() {
		try {
			String collection = "(defclass java.util.Collection (is-a USER) "
					+ "(multislot items) (slot class))";
			this.classes = this.toDefclasses(Interpreter.getClasses());
			this.classes.add(collection);
			this.rules = this.buildRules(Interpreter.getClasses());
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	private List<String> buildRules(List<Class<? extends Entity>> classes) {
		List<String> rules = new ArrayList<String>();
		for (Class<? extends Entity> clazz : classes) {
			List<Field> fields = this.getReferringFields(clazz);
			if (fields.size() < 1)
				continue;
			String cname = clazz.getName();
			StringBuilder rule = new StringBuilder("(defrule replace_")
					.append(cname).append(" \n\t?i<-(object (is-a ")
					.append(cname).append(")");
			StringBuilder fs = new StringBuilder();
			StringBuilder ms = new StringBuilder(
					"\n\t=>\n\t(modify-instance ?i");
			for (int i = 0; i < fields.size(); i++) {
				Field field = fields.get(i);
				fs.append("(f_").append(field.getName()).append(" ?v")
						.append(i).append("&:(instance-namep ?v").append(i)
						.append("))");
				ms.append("(f_").append(field.getName())
						.append(" (instance-address ?v").append(i).append("))");
			}
			rule.append(fs).append(")").append(ms).append("))");
			rules.add(rule.toString());
		}
		String crule = "(defrule replace_java.util.Collection "
				+ "\n\t?ins<-(object (is-a java.util.Collection)"
				+ "(items $?itmsb ?itm&:(instance-namep ?itm) $?itmsa)) "
				+ "\n\t=>"
				+ "\n\t(modify-instance "
				+ "?ins(items (create$ $?itmsb (instance-address ?itm) $?itmsa)))"
				+ "\n)";
		rules.add(crule);
		return rules;
	}

	private List<Field> getReferringFields(Class<? extends Entity> clazz) {
		return this.referringFields.get(clazz);
	}

	private int isStorable(Class<?> type) {
		if (EntityUtils.isSubClass(type, Entity.class)
				|| EntityUtils.isSubClass(type, Collection.class))
			return 1;
		if (type.isPrimitive())
			return 2;
		for (Class<?> clazz : storableType) {
			if (EntityUtils.isSubClass(type, clazz))
				return 3;
		}
		return 0;
	}

	public static MapManager getInstance() {
		if (manager == null)
			manager = new MapManager();
		return manager;
	}

	public List<String> toDefclasses(Class<? extends Entity>... classes) {
		return this.toDefclasses(classes);
	}

	public List<String> toDefclasses(List<Class<? extends Entity>> classes) {
		List<String> templates = new ArrayList<String>();
		for (Class<? extends Entity> clazz : classes) {
			String template = this.toDefclass(clazz);
			templates.add(template);
		}
		return templates;
	}

	public String toDefclass(Class<? extends Entity> clazz) {
		List<Field> storableFields = new ArrayList<Field>();
		List<Field> referringFields = new ArrayList<Field>();

		String superClass = this.getSuperClass(clazz);
		StringBuilder defclass = new StringBuilder("(defclass ")
				.append(clazz.getName()).append(" (is-a ").append(superClass)
				.append(")");
		List<Field> fields = EntityUtils.getFields(clazz);
		for (Field f : fields) {
			if (this.isStorable(f.getType()) > 0) {
				storableFields.add(f);
				defclass.append(" (slot f_").append(f.getName()).append(")");
			}
			if (this.isStorable(f.getType()) == 1) {
				referringFields.add(f);
			}
		}
		this.referringFields.put(clazz, referringFields);
		this.storableFields.put(clazz, storableFields);
		return defclass + " (slot class))";
	}

	private String getSuperClass(Class<?> clazz) {
		return "USER";
		/*
		 * if (this.isStorable(clazz.getSuperclass()) > 0) { return
		 * clazz.getSuperclass().getName(); } else { return "USER"; }
		 */
	}

	public List<String> getClasses() {
		return this.classes;
	}

	public List<String> getRules() {
		return this.rules;
	}

	public Set<Class<? extends Entity>> getStoredEntities() {
		return this.storableFields.keySet();
	}

	public List<Field> getStoredFields(Class<? extends Entity> clazz) {
		return this.storableFields.get(clazz);
	}
}
